﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ZoeEngine.Framework
{
    public class ComponentManager : EntityCollection
    {
        private bool _initialized;

        private List<ITicked> _tickedComponents = new List<ITicked>();
        private List<IDrawn> _drawnComponents = new List<IDrawn>();
        private List<EntityComponent> _pendingRemoval = new List<EntityComponent>();

        private void Add(EntityComponent component)
        {
            if (component is IDrawn)
            {
                IDrawn drawnComp = component as IDrawn;
                drawnComp.DrawOrderChanged += new EventHandler(ComponentDrawOrderChanged);

                _drawnComponents.Add(drawnComp);
                _drawnComponents.Sort(DrawOrderComparer.Default);
            }

            ITicked tickedComp = component as ITicked;
            tickedComp.UpdateOrderChanged += new EventHandler(ComponentUpdateOrderChanged);

            _tickedComponents.Add(tickedComp);
            _tickedComponents.Sort(UpdateOrderComparer.Default);
        }
        public override void Clear()
        {
            foreach (Entity entity in this)
            {
                RemoveImmediate(entity);
            }
        }
        public override bool Remove(Entity item)
        {
            if (item != null)
            {
                foreach (EntityComponent component in item)
                {
                    if (!_pendingRemoval.Contains(component))
                    {
                        _pendingRemoval.Add(component);
                    }
                }
            }
            return base.Remove(item);
        }
        public override bool RemoveImmediate(Entity item)
        {
            if (item != null)
            {
                item.Clear();
            }
            return base.RemoveImmediate(item);
        }
        public bool Remove(EntityComponent component)
        {
            if (!_pendingRemoval.Contains(component))
            {
                _pendingRemoval.Add(component);
                return true;
            }
            return false;
        }
        public bool RemoveImmediate(EntityComponent component)
        {
            if (component.Owner.Contains(component))
            {
                return component.Owner.RemoveImmediate(component);
            }
            return false;
        }

        protected override void OnEntityAdded(EntityEventArgs e)
        {
            Entity entity = e.Target;
            
            entity.ComponentAdded += new EventHandler<ComponentEventArgs>(EntityComponentAdded);
            foreach (EntityComponent component in entity)
            {
                Add(component);

                if (_initialized)
                {
                    component.Start();
                }
            }
            base.OnEntityAdded(e);
        }

        /// <summary>
        /// Invoked as a result of a managed entity adding a new component.
        /// </summary>
        /// <param name="sender">Origin of the invocation.</param>
        /// <param name="e">Args describing the event.</param>
        void EntityComponentAdded(object sender, ComponentEventArgs e)
        {
            Add(e.Component);
            if (_initialized)
            {
                e.Component.Start();
            }
        }
        /// <summary>
        /// Invoked as a result of a managed component's draw order being changed. Re-sorts the draw list by draw order.
        /// </summary>
        /// <param name="sender">Origin of the invocation.</param>
        /// <param name="e">Args describing the event.</param>
        void ComponentDrawOrderChanged(object sender, EventArgs e)
        {
            _drawnComponents.Sort(DrawOrderComparer.Default);
        }
        /// <summary>
        /// Invoked as a result of a managed component's update order being changed. Re-sorts the update list by update order.
        /// </summary>
        /// <param name="sender">Origin of the invocation.</param>
        /// <param name="e">Args describing the event.</param>
        void ComponentUpdateOrderChanged(object sender, EventArgs e)
        {
            _tickedComponents.Sort(UpdateOrderComparer.Default);
        }

        public void LoadContent()
        {
            foreach (IDrawn drawnComp in _drawnComponents)
            {
                drawnComp.LoadContent();
            }
        }
        public void Initialize()
        {
            foreach (Entity entity in this)
            {
                foreach (EntityComponent component in entity)
                {
                    component.Start();
                }
            }
            _initialized = true;
        }
        public void Update(GameTime gameTime)
        {
            foreach (EntityComponent ec in _pendingRemoval)
            {
                // Remove componet from its owner entity.
                ec.Owner.RemoveImmediate(ec);

                // If it is a graphical component, remove it from the draw list.
                if (ec is IDrawn)
                {
                    _drawnComponents.Remove(ec as IDrawn);
                }

                // If it is an updated component, remove it from the update list.
                if (ec is ITicked)
                {
                    _tickedComponents.Remove(ec as ITicked);
                }

                // Removal complete, drop references.
                _pendingRemoval.Remove(ec);
            }

            for (int i = 0; i < _tickedComponents.Count; i++)
            {
                _tickedComponents[i].Update(gameTime);
            }
        }
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < _drawnComponents.Count; i++)
            {
                if (_drawnComponents[i].Visible)
                {
                    _drawnComponents[i].Draw(gameTime, spriteBatch);
                }
            }
        }
        public void UnloadContent()
        {
            foreach (IDrawn drawnComp in _drawnComponents)
            {
                drawnComp.UnloadContent();
            }
        }
    }
}
